import type {UserEvent} from "@testing-library/user-event";
import type {TabsProps} from "../src";

import * as React from "react";
import {act, render, fireEvent, within, waitFor} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {focus} from "@heroui/test-utils";
import {spy, shouldIgnoreReactWarning} from "@heroui/test-utils";
import {Modal, ModalContent, ModalHeader, ModalBody, ModalFooter} from "@heroui/modal";
import {Button} from "@heroui/button";

import {Tabs, Tab} from "../src";

type Item = {
  id: string;
  label: string;
  content?: React.ReactNode;
};

let defaultItems: Item[] = [
  {
    id: "item1",
    label: "Item1 ",
    content: "Content 1",
  },
  {
    id: "item2",
    label: "Item 2",
    content: "Content 2",
  },
  {
    id: "item3",
    label: "Item 3",
    content: "Content 3",
  },
];

function getPlacementTemplate(position: TabsProps["placement"]) {
  return (
    <Tabs aria-label="Tabs static test" data-testid="tabWrapper" placement={position}>
      <Tab key="item1" title="Item 1">
        <div>Content 1</div>
      </Tab>
      <Tab key="item2" title="Item 2">
        <div>Content 2</div>
      </Tab>
      <Tab key="item3" title="Item 3">
        <div>Content 3</div>
      </Tab>
    </Tabs>
  );
}

describe("Tabs", () => {
  let user: UserEvent;

  beforeEach(() => {
    user = userEvent.setup();
  });
  afterEach(() => {
    jest.clearAllMocks();
  });

  it("should render correctly (static)", () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    expect(() => wrapper.unmount()).not.toThrow();

    if (!shouldIgnoreReactWarning(spy)) {
      expect(spy).toHaveBeenCalledTimes(0);
    }
  });

  it("should render correctly (dynamic)", () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test" items={defaultItems}>
        {(item) => (
          <Tab key={item.id} title={item.label}>
            <div>{item.content}</div>
          </Tab>
        )}
      </Tabs>,
    );

    expect(() => wrapper.unmount()).not.toThrow();
  });

  it("renders property", () => {
    const wrapper = render(
      <Tabs aria-label="Tabs property test">
        {defaultItems.map((item) => (
          <Tab key={item.id} title={item.label}>
            <div>{item.content}</div>
          </Tab>
        ))}
      </Tabs>,
    );
    const tablist = wrapper.getByRole("tablist");

    expect(tablist).toBeTruthy();
    const tabs = within(tablist).getAllByRole("tab");

    expect(tabs.length).toBe(3);

    for (let tab of tabs) {
      expect(tab).toHaveAttribute("tabindex");
      expect(tab).toHaveAttribute("aria-selected");
      const isSelected = tab.getAttribute("aria-selected") === "true";

      if (isSelected) {
        expect(tab).toHaveAttribute("aria-controls");
        const tabpanel = document.getElementById(tab.getAttribute("aria-controls")!);

        expect(tabpanel).toBeTruthy();
        expect(tabpanel).toHaveAttribute("aria-labelledby", tab.id);
        expect(tabpanel).toHaveAttribute("role", "tabpanel");
        expect(tabpanel).toHaveTextContent(defaultItems[0]?.content as string);
      }
    }
  });

  it("ref should be forwarded", () => {
    const ref = React.createRef<HTMLDivElement>();

    render(
      <Tabs ref={ref} aria-label="Tabs static test">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );
    expect(ref.current).not.toBeNull();
  });

  test("should select the correct tab with keyboard navigation", async () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tab1 = wrapper.getByRole("tab", {name: "Item 1"});
    const tab2 = wrapper.getByRole("tab", {name: "Item 2"});
    const tab3 = wrapper.getByRole("tab", {name: "Item 3"});

    expect(tab1).toHaveAttribute("aria-selected", "true");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "false");

    act(() => {
      focus(tab1);
    });

    await user.keyboard("[ArrowRight]");
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "true");
    expect(tab3).toHaveAttribute("aria-selected", "false");

    await user.keyboard("[ArrowRight]");
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "true");

    await user.keyboard("[ArrowRight]");
    expect(tab1).toHaveAttribute("aria-selected", "true");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "false");
  });

  test("should focus the correct tab with manual keyboard navigation", async () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test" keyboardActivation="manual">
        <Tab key="item1" data-testid="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" data-testid="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" data-testid="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tab1 = wrapper.getByTestId("item1");
    const tab2 = wrapper.getByTestId("item2");
    const tab3 = wrapper.getByTestId("item3");

    expect(tab1).toHaveAttribute("aria-selected", "true");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "false");

    act(() => {
      focus(tab1);
    });

    await user.keyboard("[ArrowRight]");
    expect(tab2).toHaveFocus();

    await user.keyboard("[ArrowRight]");
    expect(tab3).toHaveFocus();

    await user.keyboard("[ArrowLeft]");
    expect(tab2).toHaveFocus();

    expect(tab1).toHaveAttribute("aria-selected", "true");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "false");
  });

  it("it should work with defaultSelectedKey", () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test" defaultSelectedKey="item2">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" data-testid="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tab2 = wrapper.getByTestId("item2");

    expect(tab2).toHaveAttribute("aria-selected", "true");
  });

  it("should not select a tab when disabled", async () => {
    const wrapper = render(
      <Tabs aria-label="Tabs static test" disabledKeys={["item2"]}>
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" data-testid="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tab2 = wrapper.getByTestId("item2");

    await user.click(tab2);
    expect(tab2).toHaveAttribute("aria-selected", "false");
  });

  it("should change the position of the tabs", () => {
    const wrapper = render(getPlacementTemplate("top"));

    const tabWrapper = wrapper.getByTestId("tabWrapper").parentNode;

    expect(tabWrapper).toHaveAttribute("data-placement", "top");
    expect(tabWrapper).toHaveAttribute("data-vertical", "horizontal");

    // Test bottom position
    wrapper.rerender(getPlacementTemplate("bottom"));

    expect(tabWrapper).toHaveAttribute("data-placement", "bottom");
    expect(tabWrapper).toHaveAttribute("data-vertical", "horizontal");

    // Test start position
    wrapper.rerender(getPlacementTemplate("start"));

    expect(tabWrapper).toHaveAttribute("data-placement", "start");
    expect(tabWrapper).toHaveAttribute("data-vertical", "vertical");

    // Test end position
    wrapper.rerender(getPlacementTemplate("end"));

    expect(tabWrapper).toHaveAttribute("data-placement", "end");
    expect(tabWrapper).toHaveAttribute("data-vertical", "vertical");
  });

  it("should change the orientation of the tabs", () => {
    const wrapper = render(
      <Tabs isVertical aria-label="Tabs static test" data-testid="tabWrapper">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tabWrapper = wrapper.getByTestId("tabWrapper").parentNode;

    expect(tabWrapper).toHaveAttribute("data-placement", "start");
    expect(tabWrapper).toHaveAttribute("data-vertical", "vertical");

    // Test horizontal orientation
    wrapper.rerender(
      <Tabs aria-label="Tabs static test" data-testid="tabWrapper" isVertical={false}>
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    expect(tabWrapper).toHaveAttribute("data-placement", "top");
    expect(tabWrapper).toHaveAttribute("data-vertical", "horizontal");
  });

  test("should destroy inactive tab panels", () => {
    const {container} = render(
      <Tabs aria-label="Tabs test (destroyInactiveTabPanel=true)">
        <Tab key="tab1" data-testid="item1" title="Tab 1">
          <input className="border-2" data-testid="input" id="firstTab" />
        </Tab>
        <Tab key="tab2" data-testid="item2" title="Tab 2">
          <p id="secondTab">second tab content</p>
        </Tab>
      </Tabs>,
    );

    expect(container.querySelectorAll("[data-slot='panel']")).toHaveLength(1);
  });

  test("should not destroy inactive tab panels", async () => {
    const wrapper = render(
      <Tabs aria-label="Tabs test (destroyInactiveTabPanel=false)" destroyInactiveTabPanel={false}>
        <Tab key="tab1" data-testid="item1" title="Tab 1">
          <input className="border-2" data-testid="input" id="firstTab" />
        </Tab>
        <Tab key="tab2" data-testid="item2" title="Tab 2">
          <p id="secondTab">second tab content</p>
        </Tab>
      </Tabs>,
    );

    const {container} = wrapper;

    expect(container.querySelectorAll("[data-slot='panel']")).toHaveLength(2);

    const tab1 = wrapper.getByTestId("item1");
    const tab2 = wrapper.getByTestId("item2");
    const input = wrapper.getByTestId("input");

    fireEvent.change(input, {target: {value: "23"}});

    expect(input).toHaveValue("23");

    act(() => {
      focus(tab1);
    });

    await user.keyboard("[ArrowRight]");
    expect(tab2).toHaveFocus();

    await user.keyboard("[ArrowLeft]");
    expect(tab1).toHaveFocus();

    expect(input).toHaveValue("23");
  });

  test("should forward ref to the tab item", () => {
    const ref = React.createRef<HTMLButtonElement>();

    render(
      <Tabs aria-label="Tabs static test">
        <Tab key="item1" tabRef={ref} title="Item 1">
          <div>Content 1</div>
        </Tab>
      </Tabs>,
    );
    expect(ref.current).not.toBeNull();
  });

  it("Tab click should be handled", async () => {
    const item1Click = jest.fn();
    const item2Click = jest.fn();
    const wrapper = render(
      <Tabs>
        <Tab key="item1" data-testid="item1" title="Item 1" onClick={item1Click}>
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" data-testid="item2" title="Item 2" onClick={item2Click}>
          <div>Content 2</div>
        </Tab>
      </Tabs>,
    );
    const tab1 = wrapper.getByTestId("item1");
    const tab2 = wrapper.getByTestId("item2");

    // Test initial state
    expect(tab1).toHaveAttribute("aria-selected", "true");
    expect(tab2).toHaveAttribute("aria-selected", "false");

    // Test clicking tab2
    await user.click(tab2);
    expect(item2Click).toHaveBeenCalledTimes(1);
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "true");

    // Test clicking tab2 again
    await user.click(tab2);
    expect(item2Click).toHaveBeenCalledTimes(2);
    expect(tab2).toHaveAttribute("aria-selected", "true");
  });

  it("should allow reopening modal with tabs without blocking", async () => {
    const TestComponent = () => {
      const [isOpen, setIsOpen] = React.useState(false);

      return (
        <>
          <Button data-testid="open-modal-btn" onPress={() => setIsOpen(true)}>
            Open Modal
          </Button>
          <Modal data-testid="test-modal" isOpen={isOpen} onOpenChange={setIsOpen}>
            <ModalContent>
              {(onClose) => (
                <>
                  <ModalHeader>Test Modal with Tabs</ModalHeader>
                  <ModalBody>
                    <Tabs aria-label="Test tabs" data-testid="modal-tabs">
                      <Tab key="tab1" data-testid="tab-1" title="Tab 1">
                        <div data-testid="tab1-content">Content for Tab 1</div>
                      </Tab>
                      <Tab key="tab2" data-testid="tab-2" title="Tab 2">
                        <div data-testid="tab2-content">Content for Tab 2</div>
                      </Tab>
                      <Tab key="tab3" data-testid="tab-3" title="Tab 3">
                        <div data-testid="tab3-content">Content for Tab 3</div>
                      </Tab>
                    </Tabs>
                  </ModalBody>
                  <ModalFooter>
                    <Button data-testid="close-modal-btn" onPress={onClose}>
                      Close
                    </Button>
                  </ModalFooter>
                </>
              )}
            </ModalContent>
          </Modal>
        </>
      );
    };

    const {getByTestId, getByRole, queryByRole} = render(<TestComponent />);

    const openButton = getByTestId("open-modal-btn");

    await act(async () => {
      fireEvent.click(openButton);
    });

    await waitFor(() => {
      const modal = getByRole("dialog");

      expect(modal).toBeInTheDocument();
    });

    const tabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');

    expect(tabButtons).toHaveLength(3);

    await act(async () => {
      fireEvent.click(tabButtons[1]);
    });

    await waitFor(() => {
      expect(tabButtons[1]).toHaveAttribute("aria-selected", "true");
    });

    const closeButton = getByTestId("close-modal-btn");

    await act(async () => {
      fireEvent.click(closeButton);
    });

    await waitFor(
      () => {
        expect(queryByRole("dialog")).not.toBeInTheDocument();
      },
      {timeout: 1000},
    );

    await act(async () => {
      fireEvent.click(openButton);
    });

    await waitFor(() => {
      const modal = getByRole("dialog");

      expect(modal).toBeInTheDocument();
    });

    const newTabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');

    expect(newTabButtons).toHaveLength(3);

    await act(async () => {
      fireEvent.click(newTabButtons[2]);
    });

    await waitFor(() => {
      expect(newTabButtons[2]).toHaveAttribute("aria-selected", "true");
    });
  });

  test("should have correct aria-orientation for vertical tabs", () => {
    const wrapper = render(
      <Tabs isVertical aria-label="Vertical tabs test">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tablist = wrapper.getByRole("tablist");

    expect(tablist).toHaveAttribute("aria-orientation", "vertical");
  });

  test("should navigate vertical tabs with ArrowUp and ArrowDown keys", async () => {
    const wrapper = render(
      <Tabs isVertical aria-label="Vertical tabs keyboard test">
        <Tab key="item1" title="Item 1">
          <div>Content 1</div>
        </Tab>
        <Tab key="item2" title="Item 2">
          <div>Content 2</div>
        </Tab>
        <Tab key="item3" title="Item 3">
          <div>Content 3</div>
        </Tab>
      </Tabs>,
    );

    const tab1 = wrapper.getByRole("tab", {name: "Item 1"});
    const tab2 = wrapper.getByRole("tab", {name: "Item 2"});
    const tab3 = wrapper.getByRole("tab", {name: "Item 3"});

    act(() => {
      focus(tab1);
    });

    await user.keyboard("[ArrowDown]");
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "true");
    expect(tab3).toHaveAttribute("aria-selected", "false");

    await user.keyboard("[ArrowDown]");
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "false");
    expect(tab3).toHaveAttribute("aria-selected", "true");

    await user.keyboard("[ArrowUp]");
    expect(tab1).toHaveAttribute("aria-selected", "false");
    expect(tab2).toHaveAttribute("aria-selected", "true");
    expect(tab3).toHaveAttribute("aria-selected", "false");
  });
});
